/* 该程序可以用来构造由n个进程组成的环，n的值从命令行中获取
 * 此时共需要n个管道。需要注意的是，程序只需要一个长度为2（而不是2n）的数组来保存文件的描述符
 * 由两个进程组成的环被创建好之后，父进程就会退出，而子进程再次进行分支
 */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    pid_t childpid;     // 指示进程应该产生另一个进程
    int fd[2];          // 管道中返回的文件描述符
    int i;              // i表示this进程数（从1开始）、
    int error;          // 从dup2调用中返回值
    int nprocs;         // 环中的进程总数

    // 检查生产有效进程数的命令行
    if ( argc != 2 || ( (nprocs = atoi(argv[1]) ) <= 0) ) {
        fprintf(stderr, "Usage: %s nprocs\n", argv[0]);
        return 1;
    }

    // 通过管道将std输入连接到std输出中
    if (pipe(fd) == -1) {
        perror("Failed to create starting pipe");
        return -1;
    }

    if (dup2(fd[0], STDIN_FILENO) == -1 || dup2(fd[1], STDOUT_FILENO) == -1) {
        perror("Failed to connect pipe");
        return -1;
    }

    // 将fd[0]和fd[1]重定向到标准输入输出后，将该文件描述符关闭
    if ( (close(fd[0]) == -1) || (close(fd[1]) == -1) ) {
        perror("Failed to close extra descriptors");
        return -1;
    }

    // 创建子进程
    for (i = 1; i < nprocs; i++) {
        if (pipe(fd) == -1) {
            fprintf(stderr, "[%ld] : failed to create pipe %d: %s\n", (long)getpid(), i, strerror(errno) );
            // return -1;
            continue;       // 如果创建失败，则退出该继续创建其余子进程
        }

        if ( (childpid = fork()) == -1) {
            fprintf(stderr, "[%ld] : failed to create child %d: %s\n", (long)getpid(), i, strerror(errno));
            return -1;
        }

        // 对于父进程，重新分配stdout,即将父进程重定向到STDOUT
        if (childpid > 0)
            error = dup2(fd[1], STDOUT_FILENO);
        else    // 将子进程重定向到STDIN
        {
            error = dup2(fd[0], STDIN_FILENO);
        }
            

        if (error == -1) {
            fprintf(stderr, "[%ld] : failed to dup pipes for iterator %d: %s\n", (long)getpid(), i, strerror(errno));
            return -1;
        }

        // 因为fd[0]和fd[1]已经重定向，此时可以关闭该文件描述符
        if ( (close(fd[0]) == -1) || (close(fd[1]) == -1) ) {
            fprintf(stderr, "[%ld] : failed to close extra descriptors %d: %s\n", (long)getpid(), i, strerror(errno));
            return -1;
        }
        // 退出父进程，然后由子进程继续创建子子进程
        if (childpid) break;        
    }

    fprintf(stderr, "This is process %d with ID %ld and parent id %ld\n", i, (long)getpid(), (long)getppid());
    return 0;
}